package net.pws.common.security.spi.resource;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import net.pws.common.event.ApplicationEventBus;
import net.pws.common.event.ApplicationEventListener;
import net.pws.common.event.ApplicationEventObject;
import net.pws.common.util.StringUtils;
import net.pws.common.security.spi.event.OfflineUserEventObject;
import net.pws.common.security.spi.event.PermissionChangedEventObject;
import net.pws.common.security.spi.event.PermissionChangedType;


public class DefaultCachedResourceManager implements
                                         ICachedResourceManager,
                                         ApplicationEventListener {
    
    private ResourceEhCacheManager cacheManager;
    
    private IResourceRepository resourceRepository;
    
    public DefaultCachedResourceManager(ResourceEhCacheManager cacheManager,
                                        IResourceRepository resourceRepository) {
        this.cacheManager = cacheManager;
        this.resourceRepository = resourceRepository;
        ApplicationEventBus.getInstance().addListener(this);
    }
    
    // ***********************PermissionChangedListener&
    // OfflineUserEventObject***************************
    
    public boolean support(ApplicationEventObject event) {
        return ((event instanceof PermissionChangedEventObject) || (event instanceof OfflineUserEventObject));
    }
    
    public void handle(ApplicationEventObject event) {
        if (event instanceof PermissionChangedEventObject) {
            handle((PermissionChangedEventObject) event);
        }
        else if (event instanceof OfflineUserEventObject) {
            handle((OfflineUserEventObject) event);
        }
    }
    
    public void handle(PermissionChangedEventObject eventObject) {
        PermissionChangedType permissionChangedType = eventObject.getPermissionChangedType();
        if (StringUtils.isEmpty((String) eventObject.getSource())) {
            return;
        }
        if (permissionChangedType == PermissionChangedType.USER_CHANGED) {
            String userId = (String) eventObject.getSource();
            cacheManager.remove(userId);
        }
        else if (permissionChangedType == PermissionChangedType.ROLE_CHANGED) {
            String[] userIds = resourceRepository.byRole((String) eventObject.getSource());
            if (null == userIds) {
                return;
            }
            // TODO add copy on write lock
            for (String userId : userIds) {
                cacheManager.remove(userId);
            }
        }
    }
    
    public void handle(OfflineUserEventObject eventObject) {
        String userId = (String) eventObject.getSource();
        if (StringUtils.isEmpty(userId)) {
            return;
        }
        // TODO add copy on write lock
        cacheManager.remove(userId);
    }
    
    public ICachedResource getCachedResource(String userId) {
        ICachedResource result = (ICachedResource) cacheManager.get(userId);
        if (result != null) {
            return result;
        }
        
        return retrieveAndCacheResource(userId);
    }
    
    // **************************************************
    
    private Lock mapLock = new ReentrantLock();
    
    private Map<String, Lock> map = new ConcurrentHashMap<String, Lock>();
    
    private ICachedResource retrieveAndCacheResource(String userId) {
        Lock lock = map.get(userId);
        if (null == lock) {
            mapLock.lock();
            try {
                lock = map.get(userId);
                if (null == lock) {
                    lock = new ReentrantLock();
                    map.put(userId, lock);
                }
            }
            finally {
                mapLock.unlock();
            }
        }
        
        lock.lock();
        
        try {
            ICachedResource cachedResource = cacheManager.get(userId);
            if (null != cachedResource) {
                return cachedResource;
            }
            List<IResource> resources = resourceRepository.getHierarchicalResources(userId);
            cachedResource = new DefaultCachedResource(resources);
            cacheManager.put(userId, cachedResource);
            return cachedResource;
        }
        finally {
            lock.unlock();
        }
    }
    
}
